
/**
 * Concrete implementation of ObjectFileTokenizer.
 * <p>
 * In addition to implementing the interface, this class also contains a few static
 * methods for converting mathematical symbols into their respective enum values.<br><br>
 * 
 * NOTE: In this particular implementation, this class would be better off as static. However,
 * since it is coded through the interface ObjectFileTokenizer, there may be other concrete
 * implementations that could not be done statically.
 * 
 * @author Tobin Chee
 *
 */
public class ObjectFileTokenizer1 implements ObjectFileTokenizer {
	/**
	 * This is the set of all legal characters that may comprise tokens of type Tokenizer.TokenType.SEPARATOR, as defined by {@link ObjectFileTokenizer.TokenType.SEPARATOR}.
	 */
	private static final char[] SEPARATOR_CHARS = {'|'};
	/**
	 * This is part of the set of all legal characters that may comprise tokens of type Tokenizer.TokenType.TEXT, as defined by {@link ObjectFileTokenizer.TokenType.TEXT}.
	 * The entire set of legal characters is defined by the union of TEXT_CHARS and the set of all alpha-numeric characters.
	 */
	private static final char[] TEXT_CHARS = {'+', '-', '*', '/', ':', ' '};
	
	/**
	 * Default constructor. Create an object file tokenizer.
	 */
	public ObjectFileTokenizer1(){
	}
	
	
	/*
	 * (non-Javadoc)
	 * @see ObjectFileTokenizer#getNextNonSeparatorToken(java.lang.StringBuffer, java.lang.StringBuffer, ObjectFileTokenizer.TokenType[])
	 */
	public void getNextNonSeparatorToken(StringBuffer sourceLine,
			StringBuffer token, TokenType[] type) {
		//get first token
		this.getNextToken(sourceLine, token, type);
		//continue calling recursively till a NON-SEPARATOR token is found
		if(type[0] == ObjectFileTokenizer.TokenType.SEPARATOR){
			this.getNextNonSeparatorToken(sourceLine, token, type);
		}
		//else do nothing, allow current token and type to pass by ref
	}

	/*
	 * (non-Javadoc)
	 * @see ObjectFileTokenizer#getNextToken(java.lang.StringBuffer, java.lang.StringBuffer, ObjectFileTokenizer.TokenType[])
	 */
	public void getNextToken(StringBuffer sourceLine, StringBuffer token, TokenType[] type) {
		char ch;
		
		//clear non-empty tokens and set type to EMPTY
		if(token.length() > 0){
			token.delete(0, token.length());
		}
		type[0] = ObjectFileTokenizer.TokenType.EMPTY;
		
		if(sourceLine.length() > 0){
			ch = sourceLine.charAt(0);
			sourceLine.deleteCharAt(0);
			
			if(isSeparatorChar(ch)){
				token.append(ch);
				//sourceLine.insert(0, ch);
				type[0] = ObjectFileTokenizer.TokenType.SEPARATOR;
			}
			else{ //if ERROR or TEXT char
				token.append(ch);
				
				if(isTextChar(ch)){
					type[0] = ObjectFileTokenizer.TokenType.TEXT;
				}
				else{
					type[0] = ObjectFileTokenizer.TokenType.ERROR;
				}
	
				this.parseTillSeparator(sourceLine, token, type);
			}
		}
		//else do nothing. token is already empty and type is EMPTY
	}
	
	/** 
	 * Parse sourceLine into token until the next separator is reached.
	 * <p>
	 * The terminating separator is not included in token, and is preserved
	 * by inserting it back into sourceLine in its original location.
	 * 
	 * @param sourceLine line of source code to be tokenized.
	 * @param token string token.
	 * @param type type of token.
	 */
	private void parseTillSeparator(StringBuffer sourceLine, StringBuffer token, TokenType[] type){
		char ch;
		
		if(sourceLine.length() > 0){
			ch = sourceLine.charAt(0);
			sourceLine.deleteCharAt(0);
			
			if(isSeparatorChar(ch)){
				//preserve terminating separator and terminate method
				sourceLine.insert(0, ch);
			}
			else if(isTextChar(ch)){
				token.append(ch);
				
				if(type[0] != ObjectFileTokenizer.TokenType.ERROR){ //We don't want to override an ERROR type
					type[0] = ObjectFileTokenizer.TokenType.TEXT;
				}
				this.parseTillSeparator(sourceLine, token, type);
			}
			else{//if ERROR char
				token.append(ch);
				type[0] = ObjectFileTokenizer.TokenType.ERROR;
				this.parseTillSeparator(sourceLine, token, type);
			}
		}
		//else, do nothing. token and type are already the correct values
	}
	
	/**
	 * Determines if ch is a separator character.
	 * <p>
	 * A character is considered a separator character if any of the following conditions are true:<br>
	 * 	- ch is defined in {@link ObjectFileTokenizer1#SEPARATOR_CHARS}.
	 *
	 * @param ch the character to be tested.
	 * @return true, if ch is a character defined in {@link ObjectFileTokenizer1#SEPARATOR_CHARS}; false, otherwise.
	 * @author Tobin Chee 
	 * 
	 * Change Log:<br>
	 * 	- No Changes
	 */
	private boolean isSeparatorChar(char ch){
		
		for(char temp: SEPARATOR_CHARS){
			if(ch == temp){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Determines if ch is a text character.
	 * <p>
	 * A character is considered a text character if any of the following conditions are true:<br>
	 * 	- Character.isLetterOrDigit(ch)<br>
	 * 	- ch is defined in {@link ObjectFileTokenizer1#TEXT_CHARS}
	 * 
	 * @param ch the character to be tested.
	 * @return true, if Character.isLetterOrDigit(ch) or ch is defined in {@link ObjectFileTokenizer1#TEXT_CHARS}; false, otherwise.
	 * @author Tobin Chee
	 * 
	 * Change Log:<br>
	 *	- No Changes
	 */
	private boolean isTextChar(char ch){
		
		for(char temp: TEXT_CHARS){
			if(ch == temp){
				return true;
			}
		}
		
		if(Character.isLetterOrDigit(ch)){
			return true;
		}
		
		return false;
	}
}
